{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"float: right; margin: 20px 20px 20px 20px\"><img src=\"images/bro.png\" width=\"150px\"></div>\n",
    "\n",
    "# Zeek Network Data to Scikit-Learn\n",
    "In this notebook we're going to be using the zat Python module and explore the functionality that enables us to easily go from Zeek data to Pandas to Scikit-Learn. Once we get our data in a form that is usable by Scikit-Learn we have a wide array of data analysis and machine learning algorithms at our disposal.\n",
    "\n",
    "<div style=\"float: right; margin: 0px 0px 0px 0px\"><img src=\"images/pandas.png\" width=\"300px\"></div>\n",
    "\n",
    "### Software\n",
    "- zat: https://github.com/SuperCowPowers/zat\n",
    "- Pandas: https://github.com/pandas-dev/pandas\n",
    "- Scikit-Learn: http://scikit-learn.org/stable/index.html\n",
    "\n",
    "### Techniques\n",
    "<div style=\"float: right; margin: 20px 20px 20px 20px\"><img src=\"images/scikit.png\" width=\"220px\"></div>\n",
    "\n",
    "- One Hot Encoding: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.get_dummies.html\n",
    "- t-SNE: https://distill.pub/2016/misread-tsne/\n",
    "- Kmeans: http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html\n",
    "\n",
    "### Thanks\n",
    "- The DataFrameToMatrix() class is inspired by a great talk from Tom Augspurger at PyData Chicago 2016: https://youtu.be/KLPtEBokqQ0\n",
    "\n",
    "### Code Availability\n",
    "All this code in this notebook is from the examples/bro_to_scikit.py file in the zat repository (https://github.com/SuperCowPowers/zat). If you have any questions/problems please don't hesitate to open up an Issue in GitHub or even better submit a PR. :) \n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "zat: 0.3.6\n",
      "Pandas: 0.25.1\n",
      "Numpy: 1.16.4\n",
      "Scikit Learn Version: 0.21.2\n"
     ]
    }
   ],
   "source": [
    "# Third Party Imports\n",
    "import pandas as pd\n",
    "import sklearn\n",
    "from sklearn.manifold import TSNE\n",
    "from sklearn.decomposition import PCA\n",
    "from sklearn.discriminant_analysis import LinearDiscriminantAnalysis\n",
    "from sklearn.cluster import KMeans\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Local imports\n",
    "import zat\n",
    "from zat.log_to_dataframe import LogToDataFrame\n",
    "from zat.dataframe_to_matrix import DataFrameToMatrix\n",
    "\n",
    "# Good to print out versions of stuff\n",
    "print('zat: {:s}'.format(zat.__version__))\n",
    "print('Pandas: {:s}'.format(pd.__version__))\n",
    "print('Numpy: {:s}'.format(np.__version__))\n",
    "print('Scikit Learn Version:', sklearn.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quickly go from Zeek log to Pandas DataFrame"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>uid</th>\n",
       "      <th>id.orig_h</th>\n",
       "      <th>id.orig_p</th>\n",
       "      <th>id.resp_h</th>\n",
       "      <th>id.resp_p</th>\n",
       "      <th>proto</th>\n",
       "      <th>trans_id</th>\n",
       "      <th>query</th>\n",
       "      <th>qclass</th>\n",
       "      <th>qclass_name</th>\n",
       "      <th>...</th>\n",
       "      <th>rcode</th>\n",
       "      <th>rcode_name</th>\n",
       "      <th>AA</th>\n",
       "      <th>TC</th>\n",
       "      <th>RD</th>\n",
       "      <th>RA</th>\n",
       "      <th>Z</th>\n",
       "      <th>answers</th>\n",
       "      <th>TTLs</th>\n",
       "      <th>rejected</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ts</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:27.631940126</td>\n",
       "      <td>CZGShC2znK1sV7jdI7</td>\n",
       "      <td>192.168.33.10</td>\n",
       "      <td>1030</td>\n",
       "      <td>4.2.2.3</td>\n",
       "      <td>53</td>\n",
       "      <td>udp</td>\n",
       "      <td>44949</td>\n",
       "      <td>guyspy.com</td>\n",
       "      <td>1</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>F</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>0</td>\n",
       "      <td>54.245.228.191</td>\n",
       "      <td>36.000000</td>\n",
       "      <td>F</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:27.696868896</td>\n",
       "      <td>CZGShC2znK1sV7jdI7</td>\n",
       "      <td>192.168.33.10</td>\n",
       "      <td>1030</td>\n",
       "      <td>4.2.2.3</td>\n",
       "      <td>53</td>\n",
       "      <td>udp</td>\n",
       "      <td>50071</td>\n",
       "      <td>www.guyspy.com</td>\n",
       "      <td>1</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>F</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>0</td>\n",
       "      <td>guyspy.com,54.245.228.191</td>\n",
       "      <td>1000.000000,36.000000</td>\n",
       "      <td>F</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.060639143</td>\n",
       "      <td>CZGShC2znK1sV7jdI7</td>\n",
       "      <td>192.168.33.10</td>\n",
       "      <td>1030</td>\n",
       "      <td>4.2.2.3</td>\n",
       "      <td>53</td>\n",
       "      <td>udp</td>\n",
       "      <td>39062</td>\n",
       "      <td>devrubn8mli40.cloudfront.net</td>\n",
       "      <td>1</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>F</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>0</td>\n",
       "      <td>54.230.86.87,54.230.86.18,54.230.87.160,54.230...</td>\n",
       "      <td>60.000000,60.000000,60.000000,60.000000,60.000...</td>\n",
       "      <td>F</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.141794920</td>\n",
       "      <td>CZGShC2znK1sV7jdI7</td>\n",
       "      <td>192.168.33.10</td>\n",
       "      <td>1030</td>\n",
       "      <td>4.2.2.3</td>\n",
       "      <td>53</td>\n",
       "      <td>udp</td>\n",
       "      <td>7312</td>\n",
       "      <td>d31qbv1cthcecs.cloudfront.net</td>\n",
       "      <td>1</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>F</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>0</td>\n",
       "      <td>54.230.86.87,54.230.86.18,54.230.84.20,54.230....</td>\n",
       "      <td>60.000000,60.000000,60.000000,60.000000,60.000...</td>\n",
       "      <td>F</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.422703981</td>\n",
       "      <td>CZGShC2znK1sV7jdI7</td>\n",
       "      <td>192.168.33.10</td>\n",
       "      <td>1030</td>\n",
       "      <td>4.2.2.3</td>\n",
       "      <td>53</td>\n",
       "      <td>udp</td>\n",
       "      <td>41872</td>\n",
       "      <td>crl.entrust.net</td>\n",
       "      <td>1</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>F</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>0</td>\n",
       "      <td>cdn.entrust.net.c.footprint.net,192.221.123.25...</td>\n",
       "      <td>4993.000000,129.000000,129.000000,129.000000</td>\n",
       "      <td>F</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 22 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                                              uid      id.orig_h  id.orig_p  \\\n",
       "ts                                                                            \n",
       "2013-09-15 23:44:27.631940126  CZGShC2znK1sV7jdI7  192.168.33.10       1030   \n",
       "2013-09-15 23:44:27.696868896  CZGShC2znK1sV7jdI7  192.168.33.10       1030   \n",
       "2013-09-15 23:44:28.060639143  CZGShC2znK1sV7jdI7  192.168.33.10       1030   \n",
       "2013-09-15 23:44:28.141794920  CZGShC2znK1sV7jdI7  192.168.33.10       1030   \n",
       "2013-09-15 23:44:28.422703981  CZGShC2znK1sV7jdI7  192.168.33.10       1030   \n",
       "\n",
       "                              id.resp_h  id.resp_p proto  trans_id  \\\n",
       "ts                                                                   \n",
       "2013-09-15 23:44:27.631940126   4.2.2.3         53   udp     44949   \n",
       "2013-09-15 23:44:27.696868896   4.2.2.3         53   udp     50071   \n",
       "2013-09-15 23:44:28.060639143   4.2.2.3         53   udp     39062   \n",
       "2013-09-15 23:44:28.141794920   4.2.2.3         53   udp      7312   \n",
       "2013-09-15 23:44:28.422703981   4.2.2.3         53   udp     41872   \n",
       "\n",
       "                                                       query  qclass  \\\n",
       "ts                                                                     \n",
       "2013-09-15 23:44:27.631940126                     guyspy.com       1   \n",
       "2013-09-15 23:44:27.696868896                 www.guyspy.com       1   \n",
       "2013-09-15 23:44:28.060639143   devrubn8mli40.cloudfront.net       1   \n",
       "2013-09-15 23:44:28.141794920  d31qbv1cthcecs.cloudfront.net       1   \n",
       "2013-09-15 23:44:28.422703981                crl.entrust.net       1   \n",
       "\n",
       "                              qclass_name  ...  rcode rcode_name  AA TC RD RA  \\\n",
       "ts                                         ...                                  \n",
       "2013-09-15 23:44:27.631940126  C_INTERNET  ...      0    NOERROR   F  F  T  T   \n",
       "2013-09-15 23:44:27.696868896  C_INTERNET  ...      0    NOERROR   F  F  T  T   \n",
       "2013-09-15 23:44:28.060639143  C_INTERNET  ...      0    NOERROR   F  F  T  T   \n",
       "2013-09-15 23:44:28.141794920  C_INTERNET  ...      0    NOERROR   F  F  T  T   \n",
       "2013-09-15 23:44:28.422703981  C_INTERNET  ...      0    NOERROR   F  F  T  T   \n",
       "\n",
       "                               Z  \\\n",
       "ts                                 \n",
       "2013-09-15 23:44:27.631940126  0   \n",
       "2013-09-15 23:44:27.696868896  0   \n",
       "2013-09-15 23:44:28.060639143  0   \n",
       "2013-09-15 23:44:28.141794920  0   \n",
       "2013-09-15 23:44:28.422703981  0   \n",
       "\n",
       "                                                                         answers  \\\n",
       "ts                                                                                 \n",
       "2013-09-15 23:44:27.631940126                                     54.245.228.191   \n",
       "2013-09-15 23:44:27.696868896                          guyspy.com,54.245.228.191   \n",
       "2013-09-15 23:44:28.060639143  54.230.86.87,54.230.86.18,54.230.87.160,54.230...   \n",
       "2013-09-15 23:44:28.141794920  54.230.86.87,54.230.86.18,54.230.84.20,54.230....   \n",
       "2013-09-15 23:44:28.422703981  cdn.entrust.net.c.footprint.net,192.221.123.25...   \n",
       "\n",
       "                                                                            TTLs  \\\n",
       "ts                                                                                 \n",
       "2013-09-15 23:44:27.631940126                                          36.000000   \n",
       "2013-09-15 23:44:27.696868896                              1000.000000,36.000000   \n",
       "2013-09-15 23:44:28.060639143  60.000000,60.000000,60.000000,60.000000,60.000...   \n",
       "2013-09-15 23:44:28.141794920  60.000000,60.000000,60.000000,60.000000,60.000...   \n",
       "2013-09-15 23:44:28.422703981       4993.000000,129.000000,129.000000,129.000000   \n",
       "\n",
       "                              rejected  \n",
       "ts                                      \n",
       "2013-09-15 23:44:27.631940126        F  \n",
       "2013-09-15 23:44:27.696868896        F  \n",
       "2013-09-15 23:44:28.060639143        F  \n",
       "2013-09-15 23:44:28.141794920        F  \n",
       "2013-09-15 23:44:28.422703981        F  \n",
       "\n",
       "[5 rows x 22 columns]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create a Pandas dataframe from a Zeek log\n",
    "log_to_df = LogToDataFrame()\n",
    "bro_df = log_to_df.create_dataframe('../data/dns.log')\n",
    "\n",
    "# Print out the head of the dataframe\n",
    "bro_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"float: left; margin: 20px -10px 0px -40px\"><img src=\"images/confused.jpg\" width=\"200px\"></div>\n",
    "<div style=\"float: right; margin: 20px -10px 0px -10px\"><img src=\"images/pandas.png\" width=\"300px\"></div>\n",
    "## So... what just happened?\n",
    "**Yep it was quick... the two little lines of code above turned a Zeek log (any log) into a Pandas DataFrame. The zat package also supports streaming data from dynamic/active logs, handles log rotations and in general tries to make your life a bit easier when doing data analysis and machine learning on Zeek data.**\n",
    "\n",
    "**Now that we have the data in a dataframe there are a million wonderful things we could do for data munging, processing and analysis but that will have to wait for another time/notebook.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Using Pandas we can easily and efficiently compute additional data metrics\n",
    "# Here we use the vectorized operations of Pandas/Numpy to compute query length\n",
    "bro_df['query_length'] = bro_df['query'].str.len()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DNS records are a mix of numeric and categorical data\n",
    "When we look at the dns records some of the data is numerical and some of it is categorical so we'll need a way of handling both data types in a generalized way. zat has a DataFrameToMatrix class that handles a lot of the details and mechanics of combining numerical and categorical data, we'll use below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>AA</th>\n",
       "      <th>RA</th>\n",
       "      <th>RD</th>\n",
       "      <th>TC</th>\n",
       "      <th>Z</th>\n",
       "      <th>rejected</th>\n",
       "      <th>proto</th>\n",
       "      <th>qclass_name</th>\n",
       "      <th>qtype_name</th>\n",
       "      <th>rcode_name</th>\n",
       "      <th>query_length</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ts</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:27.631940126</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>F</td>\n",
       "      <td>0</td>\n",
       "      <td>F</td>\n",
       "      <td>udp</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>A</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>10.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:27.696868896</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>F</td>\n",
       "      <td>0</td>\n",
       "      <td>F</td>\n",
       "      <td>udp</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>A</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>14.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.060639143</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>F</td>\n",
       "      <td>0</td>\n",
       "      <td>F</td>\n",
       "      <td>udp</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>A</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>28.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.141794920</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>F</td>\n",
       "      <td>0</td>\n",
       "      <td>F</td>\n",
       "      <td>udp</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>A</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>29.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.422703981</td>\n",
       "      <td>F</td>\n",
       "      <td>T</td>\n",
       "      <td>T</td>\n",
       "      <td>F</td>\n",
       "      <td>0</td>\n",
       "      <td>F</td>\n",
       "      <td>udp</td>\n",
       "      <td>C_INTERNET</td>\n",
       "      <td>A</td>\n",
       "      <td>NOERROR</td>\n",
       "      <td>15.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                              AA RA RD TC  Z rejected proto qclass_name  \\\n",
       "ts                                                                        \n",
       "2013-09-15 23:44:27.631940126  F  T  T  F  0        F   udp  C_INTERNET   \n",
       "2013-09-15 23:44:27.696868896  F  T  T  F  0        F   udp  C_INTERNET   \n",
       "2013-09-15 23:44:28.060639143  F  T  T  F  0        F   udp  C_INTERNET   \n",
       "2013-09-15 23:44:28.141794920  F  T  T  F  0        F   udp  C_INTERNET   \n",
       "2013-09-15 23:44:28.422703981  F  T  T  F  0        F   udp  C_INTERNET   \n",
       "\n",
       "                              qtype_name rcode_name  query_length  \n",
       "ts                                                                 \n",
       "2013-09-15 23:44:27.631940126          A    NOERROR          10.0  \n",
       "2013-09-15 23:44:27.696868896          A    NOERROR          14.0  \n",
       "2013-09-15 23:44:28.060639143          A    NOERROR          28.0  \n",
       "2013-09-15 23:44:28.141794920          A    NOERROR          29.0  \n",
       "2013-09-15 23:44:28.422703981          A    NOERROR          15.0  "
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# These are the features we want (note some of these are categorical :)\n",
    "features = ['AA', 'RA', 'RD', 'TC', 'Z', 'rejected', 'proto', 'qclass_name', \n",
    "            'qtype_name', 'rcode_name', 'query_length']\n",
    "feature_df = bro_df[features]\n",
    "feature_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"float: right; margin: -10px 40px -10px 40px\"><img src=\"images/transformers.png\" width=\"200px\"></div>\n",
    "\n",
    "## Transformers\n",
    "**We'll now use a zat scikit-learn tranformer class to convert the Pandas DataFrame to a numpy ndarray (matrix). Yes it's awesome... I'm not sure it's Optimus Prime awesome.. but it's still pretty nice.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Normalizing column Z...\n",
      "Normalizing column query_length...\n"
     ]
    }
   ],
   "source": [
    "# Use the zat DataframeToMatrix class (handles categorical data)\n",
    "# You can see below it uses a heuristic to detect category data. When doing\n",
    "# this for real we should explicitly convert before sending to the transformer.\n",
    "to_matrix = dataframe_to_matrix.DataFrameToMatrix()\n",
    "bro_matrix = to_matrix.fit_transform(feature_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"float: right; margin: 0px 0px 0px 0px\"><img src=\"images/rock.gif\" width=\"150px\"></div>\n",
    "<div style=\"float: left; margin: 10px 20px 10px 10px\"><img src=\"images/scikit.png\" width=\"200px\"></div>\n",
    "\n",
    "## Scikit-Learn\n",
    "**Now that we have a numpy ndarray(matrix) we're ready to rock with scikit-learn...**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>query</th>\n",
       "      <th>proto</th>\n",
       "      <th>x</th>\n",
       "      <th>y</th>\n",
       "      <th>cluster</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ts</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:27.631940126</td>\n",
       "      <td>guyspy.com</td>\n",
       "      <td>udp</td>\n",
       "      <td>39.936161</td>\n",
       "      <td>-18.896549</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:27.696868896</td>\n",
       "      <td>www.guyspy.com</td>\n",
       "      <td>udp</td>\n",
       "      <td>24.945557</td>\n",
       "      <td>3.408248</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.060639143</td>\n",
       "      <td>devrubn8mli40.cloudfront.net</td>\n",
       "      <td>udp</td>\n",
       "      <td>-28.303942</td>\n",
       "      <td>2.064817</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.141794920</td>\n",
       "      <td>d31qbv1cthcecs.cloudfront.net</td>\n",
       "      <td>udp</td>\n",
       "      <td>-22.633984</td>\n",
       "      <td>6.704806</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2013-09-15 23:44:28.422703981</td>\n",
       "      <td>crl.entrust.net</td>\n",
       "      <td>udp</td>\n",
       "      <td>18.326878</td>\n",
       "      <td>-2.018499</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                       query proto          x          y  cluster\n",
       "ts                                                                                               \n",
       "2013-09-15 23:44:27.631940126                     guyspy.com   udp  39.936161 -18.896549        0\n",
       "2013-09-15 23:44:27.696868896                 www.guyspy.com   udp  24.945557   3.408248        0\n",
       "2013-09-15 23:44:28.060639143   devrubn8mli40.cloudfront.net   udp -28.303942   2.064817        0\n",
       "2013-09-15 23:44:28.141794920  d31qbv1cthcecs.cloudfront.net   udp -22.633984   6.704806        0\n",
       "2013-09-15 23:44:28.422703981                crl.entrust.net   udp  18.326878  -2.018499        0"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Now we're ready for scikit-learn!\n",
    "# Just some simple stuff for this example, KMeans and TSNE projection\n",
    "kmeans = KMeans(n_clusters=5).fit_predict(bro_matrix)\n",
    "projection = TSNE().fit_transform(bro_matrix)\n",
    "\n",
    "# Now we can put our ML results back onto our dataframe!\n",
    "bro_df['x'] = projection[:, 0] # Projection X Column\n",
    "bro_df['y'] = projection[:, 1] # Projection Y Column\n",
    "bro_df['cluster'] = kmeans\n",
    "bro_df[['query', 'proto', 'x', 'y', 'cluster']].head()  # Showing the scikit-learn results in our dataframe"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Plotting defaults\n",
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams['font.size'] = 18.0\n",
    "plt.rcParams['figure.figsize'] = 15.0, 7.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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Mcdo7dq1B73++uuva8OGDbrgggsOrCBK0mWXXabPfvazuuWWW3TppZce9Gd6+eWX9b3vfU/r1q3LaO6vvfaa/uqv/irha3V1dfrzn/+c0fcHfKmra3o73swK1l6+uLcdr73drW6tWlW4+SI5UQtYgiysVYpn4rMG8AUCS7/Zvt3dZUtHVZU7wefB22+/rSOPPDIvP8tTWVmpZ599Vs8884yOP/74lMffd9992rt3rz796U9r586dB7129tln69vf/ra2bt160EpnTU2NLrroooTfL5W2KSMjIyqdpf9bLBbTSLJFFoCgCECuODIQlYAF/sdnDeAb5Fj6zeioVJxmvF9U5MbnwaJFizQ0NJSXn+W59dZbtWvXLp1wwgk69thjtW7dOj344IPav39/UuOfe+45SdLpp5+u2trag74++MEPSpLeeOONg8Yce+yxKioqynju5eXl2rt3b8LXxsbGVJ5sPi0QBAHJFQcQcHzWAL7CiqXflJW5fIBZVrfmNDnpxufB8ccfr0ceeUQvvfRSRtthZxbW8STqf/nRj35UPT09euihh9TR0aGtW7fq3nvv1Zo1a7R161aVlJTM+bO8XMgf/OAHWrp0acJjZv5ZshXwHXXUUerr60v4Wl9fn+rq6rLycwBfCEiuOCBrp7f0bt8+vaW3sdH13py5rRL+wmcN4CsEln7T2OiSzBcsSH3s4KDLb8mD8847T4888ojuuece3XjjjWl/n5qaGg0MDBzy/EsvvTTr8WvXrtXatWtlrdXVV1+tm266SQ8++KDOP//8OX/Wu971LknSkiVLdPrpp6c953ScfPLJ+tGPfqRXX331oAI+r776ql577TWdc845eZ0PkFMByRVHxFHsJfj4rAF8ha2wftPc7E5qqW7PsNaNa27OzbxmWLdunRobG3XzzTfrwQcfTHjMU089pTvvvHPO77NixQo9//zzB63m7d27V3fcccdBx01OTmpwcPCg54wxWjlVIj0+OF24cGHCYPXjH/+4SktLdd1112k0wZbh3bt3z7pdNZFU2o383d/9nSS3nTee9/9///d/n/TPBXwvILniiLCuLlfEZdcuFzzW1bkbul6hl7o697xX7KWrq9AzRiJ81gC+woql39TXT5/MamqSH+edHFPdDpKm8vJy/eIXv9CZZ56pj33sYzrjjDP0wQ9+UIsXL1Z/f7+2bdumX/7yl/rSl7405/e54oor9JOf/ESnn366LrnkEu3bt09btmw5ZAvq0NCQli5dqnPOOUcrV67UEUccoZdffll33XWXqqurdfbZZx849pRTTtHWrVv1jW98Q8uWLZMxRhdccIGOPvpo3XXXXVq3bp2OO+44tba2qr6+Xv39/frjH/+oBx54QH/605/UkOTdy1TajZx55pk666yzdMstt2j37t163/vep9/97ne69957tXbtWq3O00ozkBejo27lJx15zBVHRFHsJTz4rAF8hcDSb4xx227a292HZTI5fiMj0vCw6xmWx1yQ5cuX6/e//72++93v6v7771d7e7uGh4dVU1OjpqYmbd68WZ/4xCfm/B7vf//7tWnTJt1444364he/qLq6Ol166aVqamrSaaedduC48vJyfe5zn9Ovf/1rbd26VcPDwwcCzWuuuUZHHXXUgWPvvPNOXX755Wpvbz9QYOiCCy6QJF100UVasWKFbr75Zn33u9/V4OCglixZosbGRt1www16xzvekYO/KednP/uZvva1r+mHP/yhtmzZorq6On31q1/V1VdfnbOfCRREQHLFEUGZFntpayPn0k/4rAF8xSSz2gKpqanJdnZ2znnMc889p+OOOy47PzC+J1N1deITmdeTaXjY9WSa2haKcMrq+wvIpc2bXY5aOkWp+vpcrviFF2Z/XkBPjwsOUy3K4xX5aWsjJ89P+KwB8s4Y85S1tinRa6xY+tWqVdK11x5aWKCoyN1lGxx0d+nq691KJdtzAPhFc7MrfGJt6hfvecwVRwRR7CVc+Kw5PCofI48ILP1s+XJ3d9T7QNixY/oDYfVqqaVFWraMDwQA/hKQXHFEEMVewoXPmrlR+Rh5RmDpd8a4u6PcIQUQFAHKFUfEUOwlXPismV18StXMVUmv+rGXUtXe7lKqVq0q3HwRCrQbAQBk3/Ll7kKlv18aGJi9hZK17vWdO93x3DVHLpWVSRMT6Y2l2Is/8VlzqPjKxzU1swfQXuXj2lpX+bi7O7/zROgQWAIAcsPLFa+udlux+vqkPXuksTH32Nfnnq+udq0cKECGXGtsdDUK0jE4KK1Ykd35IDv4rJmWaeVjinoiA2yFBQDkDrni8BOKvYQXnzVOb6/7SjV/1AvKe3tJv0LaCCwBALlFrvjhUbkxPyj2Em581lD5GAVFYAkAQCFRuTF/KPaC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m4JyPjj++OP1yCOP6KWXXspoO+zMwjqeRP0vP/rRj6qnp0cPPfSQOjo6tHXrVt17771as2aNtm7dqpKSkjl/lre6+IMf/EBLly5NeMzMP0t5shWUACAdVG5EtjU2umbXCxakPnZw0BV1AYA0BDKwDLOgnA/OO+88PfLII7rnnnt04403pv19ampqNDBwaGvSl156adbj165dq7Vr18paq6uvvlo33XSTHnzwQZ1//vlz/qx3vetdkqQlS5bMuWoJAHnlVW7s7XWJ8jt2uO0nZWXuQ72lRVq2jO2vSA6FoQAUSCBzLMOsudl9rqeaupfv88G6devU2Niom2++WQ8++GDCY5566indeeedc36fFStW6Pnnn1df33THl7179+qOO+446LjJyUkNzijNb4zRypUrJemg4HThwoUJg9WPf/zjKi0t1XXXXafRBHuGd+/erb17DyksPKtU2o0AwJy8yo0XXii1t0u33OIeL7zQrVYSVCJZ8YWhUkFhKAAZYsXSZ4JSKLC8vFy/+MUvdOaZZ+pjH/uYzjjjDH3wgx/U4sWL1d/fr23btumXv/ylvvSlL835fa644gr95Cc/0emnn65LLrlE+/bt05YtWw7Zgjo0NKSlS5fqnHPO0cqVK3XEEUfo5Zdf1l133aXq6mqdffbZB4495ZRTtHXrVn3jG9/QsmXLZIzRBRdcoKOPPlp33XWX1q1bp+OOO06tra2qr69Xf3+//vjHP+qBBx7Qn/70JzUkWVY31XYjt99++4HgeHx8XL29vfra174mSTrppJMO+jMAAJAWrzBUe7sr2pBMSodXGGrDBm5iAEiboaplcpqammxn59ztLp977jkdd9xxGf+s7m53PqitTf58sHOntHFj/tNvRkZG9N3vflf333+/nn32WQ0PD6umpkZNTU264IIL9IlPfOJA8ZuWlhb19PSop6fnoO+xefNm3XjjjXr55ZdVV1enSy+9VE1NTTrttNP0L//yL/rUpz6lffv26brrrtOvf/1rvfjiixoeHtbSpUt16qmn6pprrjmwzVWSXnjhBV1++eV6/PHHDxQYin+f//a3v9XNN9+s3/72txocHNSSJUvU2Nios846S5dffrliU9WTGhoa1NDQoIcffjjhn72hoSGlwNI7PpELL7xQmzZtmnN8tt5fAIAI6OpKvTDU1C4gAJiNMeYpa21TwtcILJOTz8BS4nyAQxFYAgBS0t2dXGGo1lYKQwFIylyBJVthfYpCgQCAyLB2unjR9u3TxYsaG13xAPJM00NhKAB5RGDpY5wPAAChl2hVLRZzd08fe8xVOGVVLX1eYagk6wcAQLoILH2O8wEAILTi8z5mrkqWlrreW17eR3u7y/tYtapw8wUAzIrAEgAA5F93twsqD1epzhhXJj0Wk26/vTCV6hBMbLEG8orAEgAA5Je1bvtrRUVy5c8ld9zChW5cWxsBAebGFmsg7+YVegIAACBienvdV3V1auOqq6fHArPp6nJbp70m33V1blu1t726rm66aXh7uzseQMYILAEAQH51dLhVpFRXHY1x4zo6cjMvBF/8FuuamtnfY94W69pat8W6uzu/8wRCiMASAADk1/btbmtiOqqqXJl0YKZMt1jT2x3ICIElAADIr9FRqTjNMg9FRW48MBNbrIGCIrAEAAD5VVYmTUykN3Zy0o0HZmKLNVBQBJYAACC/GhulwcH0xg4OSitWZHc+CAe2WAMFRWAJAADyq7nZtX1INafNWjeuuTk380KwscUaKCj6WPqctVa9u3vV0dOh7W9t1+jEqMqKy9S4uFHNDc2qr6yXCUgvr5aWFvX09Kinp6fQUwEAFFJ9/XS7h5qa5Md57SPq63M3NwRXWZm78VBamvpYtlgDGWPF0se6B7rV9nCb2h5u02OvPKbxyXHFimIanxzXY688duC17oHClcgeGRnRrbfeqjVr1qimpkbz58/XkUceqY985CPatGmTJtLNoclAW1ubHnjggbz/3Ll0dXXpC1/4glatWqXq6mpVV1fr5JNP1p133qnx8fFCTw8A8ssY15h+aEgaGUluzMiINDzsxgXkhiryjC3WQEERWPpU1+tdan+0XbvGdqm+sl51i+q0oGSBSotLtaBkgeoW1am+sl67xnap/dF2db2e/+a+3d3dWrlypa688krFYjFdc801uvvuu3XVVVdpfHxcF110kTZu3Jj3eV1//fW+Cyxvuukmbdq0SatWrdKNN96oG264QYsXL9bll1+us846S5YS5wCiZvlyaf16qb9fGhiYfVuste71nTvd8cuX53eeCA62WAMFxVZYH+oe6NZtT9ym2vJalc+fvQ+TMUY1ZTWKFcd0+xO3a+OajVpek58T7ujoqM466yy99NJLuv/++3Xuuece9PqXv/xlPfnkk3ryySfzMp98GRoaUkVFRcrj1q9fr02bNikWix147oorrtDatWv1ox/9SP/+7/+us846K5tTBQD/W7VKuvZa10Owt9dV5qyqcvluk5P/f3v3Hh1lle55/LtzrSSEJAWIAVdSDYE0pwWFE20cyRAF8SgddTVi03aCg6KHi6BHGxQ408b2hPFCO6xGQRAVTI9ttzBC63jGMSpB5hyHazMiEAxQxTRNK5ckJJCQ254/KolJqIRcqyrw+6xVq8y+vO9T8aVST+397u0dRaqu9k59nT9fSaW0TVOsRQJKI5ZBxlpL3t48YiNi20wqm4oOj6ZPRB/y9ub5beRr7dq1FBYW8uSTT16UVDa44YYbmDNnTpvHcblcZGRkXFS+ZcsWjDGsW7eusayyspKcnBxSU1OJjo4mPj6ekSNHsmDBAgDcbnfj/abr16/HGNP4aCo/P59JkyYRHx+Pw+Fg1KhRvPbaa63GtmfPHm6//Xbi4uIYNWpUY/3hw4c5ePBgm6+vwc0339wsqWzws5/9DIB9+/a16zgiIpedlBTIyfE+xo2DiAioqvI+jxsHzz7rrVNSKZeiKdYiAaURyyDjKfXgKfWQHNexb80SHAmNfV3xrp4JrokNGzYA8Mgjj/T4uRrMnTuXN998k+nTp/PEE09QU1PDN998w2effQbAgAEDyMvLIzs7m/T0dJ+xrVmzhlmzZjF27FiWLFlCTEwMn3zyCbNnz+bw4cO89NJLzdofO3aMW2+9lalTpzJlyhTKy8sb6yZMmIDH4+lSMv+Xv/wFgIEDB3b6GCIivZ4x4HJ5HyJd0TDFesUKiI2FhATfCaO13pHK8nJNsRbpJkosg0yBu4DwkPAOr/RqjCE8JJwCdwGu6109E1wT+/bto2/fvgwZMqTHz9Xg/fff54477mD9+vU+62NiYsjKyiI7O5shQ4aQlZXVrP7EiRPMnz+fadOm8c477zSWz5kzh8cee4yXX36Z2bNnN3tNR48e5fXXX2fmzJnd/nrKy8t56aWXiIuL4+677+7244uIiFyRNMVaJCA0FTbIFJ4uJN7Ruc194x3xHDrtn819z54926l7DbsiLi6Or7/+utPTRjds2MCFCxd46KGHOHXqVLNHZmYmdXV15OfnN+vjdDqZMWOGz+O53e5Oj1bW1taSlZXF0aNHWbVqFc6O3AsiIiIibdMUaxG/04hlkKmoqcARevG9eO0RGhJKRY1/Nvft27cvZWVlfjlXg+XLl5Odnc3IkSMZMmQIt9xyC5mZmWRmZhIScunvSA4cOADAxIkTW23z7bffNvt56NChhIaGdi3wFurq6njwwQfZvHkzubm5/PznP+/W44uIiAiaYi3iZ0osg0xUWBTVtdVE0vHNfWvraokK88/mvtdeey1bt27lyJEjXZoO29qUX1/7X95999243W4++ugjCgoKyM/P54033iA9PZ38/HwiIiLaPFfD6OLbb79NYmKizzYtX0t0dPsWUGqvuro6Zs6cydtvv80zzzwTkO1YRERERES6mxLLIJPaL5Vtx7YRExHT4b4llSWMSxrXA1FdbMqUKWzdupW1a9eydOnSTh/H6XRy5syZi8qPHDnSavusrCyysrKw1vL000/z4osvsnnzZqZOndrmuYYNGwZA//792xy17CkNSeVbb73FP//zP5OTk+P3GEREpBey1nuvYEEBFBZCRQVERUFqqnfvxeRkrWgqIgGneyyDzHjXeKrrqjt87561luq6asa7/LO578yZM0lNTWXZsmVs3rzZZ5tdu3axcuXKNo8zfPhwDh48yPHjxxvLLly4wKuvvtqsXW1tLSUlJc3KjDGMHj0aoFly2qdPH5/J6n333UdkZCTPPPMMFRUXTxkuLS3lwoULbcbbVEe2G7HW8vDDD/PWW2+xePFinnvuuXafR0RErmBFRd/fK7htm3fRGYfD+7xt2/d1RUWBjVNErngasQwyyXHJJMclU1xZjDOq/Qu6FFcWN/b1h+joaD788EMmT57MPffcw6RJk7jtttvo168fJ0+e5PPPP+fjjz9m4cKFbR7n0Ucf5d1332XixInMmjWLqqoq8vLyLpqCWlZWRmJiInfddRejR4/mqquualz4JiEhgczMzMa2Y8eOJT8/nxdeeIGkpCSMMUybNo1rrrmGVatWMXPmTEaMGEF2djbJycmcPHmSr776ik2bNrF//35c7bwXoyPbjSxYsIA333yT6667jhEjRvC73/2uWf3QoUO56aab2nVeERG5Quze/f22GS1HJSMjISbm+20zcnO922aMGRO4eEXkiqbEMsgYY8i+LpvcL3JxhDmIDr/0PX7nq89TXlXO/B/P7/A2JV2RkpLCnj17WL16NRs3biQ3N5fy8nKcTidpaWmsX7+e+++/v81j3Hzz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\n",
      "text/plain": [
       "<Figure size 1080x504 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Now use dataframe group by cluster\n",
    "cluster_groups = bro_df.groupby('cluster')\n",
    "\n",
    "# Plot the Machine Learning results\n",
    "fig, ax = plt.subplots()\n",
    "colors = {0:'red', 1:'blue', 2:'green', 3:'orange', 4:'purple', 5:'black'}\n",
    "for key, group in cluster_groups:\n",
    "    group.plot(ax=ax, kind='scatter', x='x', y='y', alpha=0.5, s=250,\n",
    "               label='Cluster: {:d}'.format(key), color=colors[key])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"float: right; margin: 20px 20px 20px 20px\"><img src=\"images/eyeball.jpeg\" width=\"200px\"></div>\n",
    "\n",
    "## Lets Investigate the 5 clusters of DNS data\n",
    "**We cramed a bunch of features into the clustering algorithm. The features were both numerical and categorical. So did the clustering 'do the right thing'? Well first some caveats and disclaimers:** \n",
    "- We're obviously working with a small amount of Zeek DNS data\n",
    "- This is an example to show how the tranformations work (from Zeek to Pandas to Scikit)\n",
    "- The DNS data is real data but for this example and others we obviously pulled in 'weird' stuff on purpose\n",
    "- We knew that the K in KMeans should be 5 :)\n",
    "\n",
    "**Okay will all those caveats lets look at how the clustering did on both numeric and categorical data combined**\n",
    "### Cluster details\n",
    "- Cluster 0: (42 observations) Looks like 'normal' DNS requests\n",
    "- Cluster 1: (11 observations) All the queries are '-' (Zeek for NA/not found/etc)\n",
    "- Cluster 2: ( 6 observations) The protocol is TCP instead of the normal UDP\n",
    "- Cluster 3: ( 4 observations) All the DNS queries are exceptionally long\n",
    "- Cluster 4: ( 4 observations) The reserved Z bit is set to 1 (required to be 0)\n",
    "\n",
    "## Numerical + Categorical = AOK\n",
    "With our example data we've successfully gone from Zeek logs to Pandas to scikit-learn. The clusters appear to make sense and certainly from an investigative and threat hunting perspective being able to cluster the data and use PCA for dimensionality reduction might come in handy depending on your use case "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Cluster 0: 42 observations\n",
      "                                                       query  Z proto qtype_name  cluster\n",
      "ts                                                                                       \n",
      "2013-09-15 23:44:27.631940126                     guyspy.com  0   udp          A        0\n",
      "2013-09-15 23:44:27.696868896                 www.guyspy.com  0   udp          A        0\n",
      "2013-09-15 23:44:28.060639143   devrubn8mli40.cloudfront.net  0   udp          A        0\n",
      "2013-09-15 23:44:28.141794920  d31qbv1cthcecs.cloudfront.net  0   udp          A        0\n",
      "2013-09-15 23:44:28.422703981                crl.entrust.net  0   udp          A        0\n",
      "\n",
      "Cluster 1: 3 observations\n",
      "                              query  Z proto qtype_name  cluster\n",
      "ts                                                              \n",
      "2013-09-15 23:44:50.827882051   NaN  0   udp        NaN        1\n",
      "2013-09-15 23:44:50.829301834   NaN  0   udp        NaN        1\n",
      "2013-09-15 23:44:51.026231050   NaN  0   udp        NaN        1\n",
      "\n",
      "Cluster 2: 3 observations\n",
      "                                       query  Z proto qtype_name  cluster\n",
      "ts                                                                       \n",
      "2013-09-15 23:48:05.897021055  j.maxmind.com  0   tcp          A        2\n",
      "2013-09-15 23:48:06.897021055  j.maxmind.com  0   tcp          A        2\n",
      "2013-09-15 23:48:07.897021055  j.maxmind.com  0   tcp          A        2\n",
      "\n",
      "Cluster 3: 3 observations\n",
      "                                                                           query  Z proto qtype_name  cluster\n",
      "ts                                                                                                           \n",
      "2013-09-15 23:48:02.497021198  superlongcrazydnsqueryforoutlierdetectionj.max...  0   udp          A        3\n",
      "2013-09-15 23:48:02.597021103  xyzqsuperlongcrazydnsqueryforoutlierdetectionj...  0   udp          A        3\n",
      "2013-09-15 23:48:02.697021008  abcsuperlongcrazydnsqueryforoutlierdetectionj....  0   udp          A        3\n",
      "\n",
      "Cluster 4: 3 observations\n",
      "                                       query  Z proto qtype_name  cluster\n",
      "ts                                                                       \n",
      "2013-09-15 23:48:02.897021055  j.maxmind.com  1   udp          A        4\n",
      "2013-09-15 23:48:04.897021055  j.maxmind.com  1   udp          A        4\n",
      "2013-09-15 23:48:04.997021198  j.maxmind.com  1   udp          A        4\n"
     ]
    }
   ],
   "source": [
    "# Now print out the details for each cluster\n",
    "pd.set_option('display.width', 1000)\n",
    "show_fields = ['query', 'Z', 'proto', 'qtype_name', 'cluster']\n",
    "for key, group in cluster_groups:\n",
    "    print('\\nCluster {:d}: {:d} observations'.format(key, len(group)))\n",
    "    print(group[show_fields].head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Wrap Up\n",
    "Well that's it for this notebook, we'll have an upcoming notebook that addresses some of the issues that we overlooked in this simple example. We use Isolation Forest for anomaly detection which works well for high dimensional data. The notebook will cover both training and streaming evalution against the model.\n",
    "\n",
    "If you liked this notebook please visit the [zat](https://github.com/SuperCowPowers/zat) project for more notebooks and examples."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
